/**
 * @class w3cRange  - w3c text range emulation for "strange" browsers
 *
 * @param  elRTE  rte  объект-редактор
 *
 * @author:    Dmitry Levashov (dio) dio@std42.ru
 * Copyright: Studio 42, http://www.std42.ru
 **/
(function ($) {
    elRTE.prototype.w3cRange = function (rte) {
        var self = this;
        this.rte = rte;
        this.r = null;
        this.collapsed = true;
        this.startContainer = null;
        this.endContainer = null;
        this.startOffset = 0;
        this.endOffset = 0;
        this.commonAncestorContainer = null;

        this.range = function () {
            try {
                this.r = this.rte.window.document.selection.createRange();
            } catch (e) {
                this.r = this.rte.doc.body.createTextRange();
            }
            return this.r;
        }

        this.insertNode = function (html) {
            this.range();
            self.r.collapse(false)
            var r = self.r.duplicate();
            r.pasteHTML(html);
        }

        this.getBookmark = function () {
            this.range();
            if (this.r.item) {
                var n = this.r.item(0);
                this.r = this.rte.doc.body.createTextRange();
                this.r.moveToElementText(n);
            }
            return this.r.getBookmark();
        }

        this.moveToBookmark = function (bm) {
            this.rte.window.focus();
            this.range().moveToBookmark(bm);
            this.r.select();
        }

        /**
         * Обновляет данные о выделенных нодах
         *
         * @return void
         **/
        this.update = function () {

            function _findPos(start) {
                var marker = '\uFEFF';
                var ndx = offset = 0;
                var r = self.r.duplicate();
                r.collapse(start);
                var p = r.parentElement();
                if (!p || p.nodeName == 'HTML') {
                    return {parent:self.rte.doc.body, ndx:ndx, offset:offset};
                }

                r.pasteHTML(marker);

                childs = p.childNodes;
                for (var i = 0; i < childs.length; i++) {
                    var n = childs[i];
                    if (i > 0 && (n.nodeType !== 3 || childs[i - 1].nodeType !== 3)) {
                        ndx++;
                    }
                    if (n.nodeType !== 3) {
                        offset = 0;
                    } else {
                        var pos = n.nodeValue.indexOf(marker);
                        if (pos !== -1) {
                            offset += pos;
                            break;
                        }
                        offset += n.nodeValue.length;
                    }
                }
                ;
                r.moveStart('character', -1);
                r.text = '';
                return {parent:p, ndx:Math.min(ndx, p.childNodes.length - 1), offset:offset};
            }

            this.range();
            this.startContainer = this.endContainer = null;

            if (this.r.item) {
                this.collapsed = false;
                var i = this.r.item(0);
                this.setStart(i.parentNode, this.rte.dom.indexOf(i));
                this.setEnd(i.parentNode, this.startOffset + 1);
            } else {
                this.collapsed = this.r.boundingWidth == 0;
                var start = _findPos(true);
                var end = _findPos(false);

                start.parent.normalize();
                end.parent.normalize();
                start.ndx = Math.min(start.ndx, start.parent.childNodes.length - 1);
                end.ndx = Math.min(end.ndx, end.parent.childNodes.length - 1);
                if (start.parent.childNodes[start.ndx].nodeType && start.parent.childNodes[start.ndx].nodeType == 1) {
                    this.setStart(start.parent, start.ndx);
                } else {
                    this.setStart(start.parent.childNodes[start.ndx], start.offset);
                }
                if (end.parent.childNodes[end.ndx].nodeType && end.parent.childNodes[end.ndx].nodeType == 1) {
                    this.setEnd(end.parent, end.ndx);
                } else {
                    this.setEnd(end.parent.childNodes[end.ndx], end.offset);
                }
                // this.dump();
                this.select();
            }
            return this;
        }

        this.isCollapsed = function () {
            this.range();
            this.collapsed = this.r.item ? false : this.r.boundingWidth == 0;
            return this.collapsed;
        }

        /**
         * "Схлопывает" выделение
         *
         * @param  bool  toStart - схлопывать выделение к началу или к концу
         * @return void
         **/
        this.collapse = function (toStart) {
            this.range();
            if (this.r.item) {
                var n = this.r.item(0);
                this.r = this.rte.doc.body.createTextRange();
                this.r.moveToElementText(n);
            }
            this.r.collapse(toStart);
            this.r.select();
            this.collapsed = true;
        }

        this.getStart = function () {
            this.range();
            if (this.r.item) {
                return this.r.item(0);
            }
            var r = this.r.duplicate();
            r.collapse(true);
            var s = r.parentElement();
            return s && s.nodeName == 'BODY' ? s.firstChild : s;
        }


        this.getEnd = function () {
            this.range();
            if (this.r.item) {
                return this.r.item(0);
            }
            var r = this.r.duplicate();
            r.collapse(false);
            var e = r.parentElement();
            return e && e.nodeName == 'BODY' ? e.lastChild : e;
        }


        /**
         * Устанавливает начaло выделения на указаную ноду
         *
         * @param  Element  node    нода
         * @param  Number   offset  отступ от начала ноды
         * @return void
         **/
        this.setStart = function (node, offset) {
            this.startContainer = node;
            this.startOffset = offset;
            if (this.endContainer) {
                this.commonAncestorContainer = this.rte.dom.findCommonAncestor(this.startContainer, this.endContainer);
            }
        }

        /**
         * Устанавливает конец выделения на указаную ноду
         *
         * @param  Element  node    нода
         * @param  Number   offset  отступ от конца ноды
         * @return void
         **/
        this.setEnd = function (node, offset) {
            this.endContainer = node;
            this.endOffset = offset;
            if (this.startContainer) {
                this.commonAncestorContainer = this.rte.dom.findCommonAncestor(this.startContainer, this.endContainer);
            }
        }

        /**
         * Устанавливает начaло выделения перед указаной нодой
         *
         * @param  Element  node    нода
         * @return void
         **/
        this.setStartBefore = function (n) {
            if (n.parentNode) {
                this.setStart(n.parentNode, this.rte.dom.indexOf(n));
            }
        }

        /**
         * Устанавливает начaло выделения после указаной ноды
         *
         * @param  Element  node    нода
         * @return void
         **/
        this.setStartAfter = function (n) {
            if (n.parentNode) {
                this.setStart(n.parentNode, this.rte.dom.indexOf(n) + 1);
            }
        }

        /**
         * Устанавливает конец выделения перед указаной нодой
         *
         * @param  Element  node    нода
         * @return void
         **/
        this.setEndBefore = function (n) {
            if (n.parentNode) {
                this.setEnd(n.parentNode, this.rte.dom.indexOf(n));
            }
        }

        /**
         * Устанавливает конец выделения после указаной ноды
         *
         * @param  Element  node    нода
         * @return void
         **/
        this.setEndAfter = function (n) {
            if (n.parentNode) {
                this.setEnd(n.parentNode, this.rte.dom.indexOf(n) + 1);
            }
        }

        /**
         * Устанавливает новое выделение после изменений
         *
         * @return void
         **/
        this.select = function () {
            // thanks tinymice authors
            function getPos(n, o) {
                if (n.nodeType != 3) {
                    return -1;
                }
                var c = '\uFEFF';
                var val = n.nodeValue;
                var r = self.rte.doc.body.createTextRange();
                n.nodeValue = val.substring(0, o) + c + val.substring(o);
                r.moveToElementText(n.parentNode);
                r.findText(c);
                var p = Math.abs(r.moveStart('character', -0xFFFFF));
                n.nodeValue = val;
                return p;
            }

            ;

            this.r = this.rte.doc.body.createTextRange();
            var so = this.startOffset;
            var eo = this.endOffset;
            var s = this.startContainer.nodeType == 1
                ? this.startContainer.childNodes[Math.min(so, this.startContainer.childNodes.length - 1)]
                : this.startContainer;
            var e = this.endContainer.nodeType == 1
                ? this.endContainer.childNodes[Math.min(so == eo ? eo : eo - 1, this.endContainer.childNodes.length - 1)]
                : this.endContainer;

            if (this.collapsed) {
                if (s.nodeType == 3) {
                    var p = getPos(s, so);
                    this.r.move('character', p);
                } else {
                    this.r.moveToElementText(s);
                    this.r.collapse(true);
                }
            } else {
                var r = this.rte.doc.body.createTextRange();
                var sp = getPos(s, so);
                var ep = getPos(e, eo);
                if (s.nodeType == 3) {
                    this.r.move('character', sp);
                } else {
                    this.r.moveToElementText(s);
                }
                if (e.nodeType == 3) {
                    r.move('character', ep);
                } else {
                    r.moveToElementText(e);
                }
                this.r.setEndPoint('EndToEnd', r);
            }

            try {
                this.r.select();
            } catch (e) {

            }
            if (r) {
                r = null;
            }
        }

        this.dump = function () {
            this.rte.log('collapsed: ' + this.collapsed);
            //this.rte.log('commonAncestorContainer: '+this.commonAncestorContainer.nodeName||'#text')
            this.rte.log('startContainer: ' + (this.startContainer ? this.startContainer.nodeName : 'non'));
            this.rte.log('startOffset: ' + this.startOffset);
            this.rte.log('endContainer: ' + (this.endContainer ? this.endContainer.nodeName : 'none'));
            this.rte.log('endOffset: ' + this.endOffset);
        }

    }
})(jQuery);
